package redis

import (
	"context"
	"time"

	"code.byted.org/bytertc/whiteboard_server/biz/pkg/kv"
	"code.byted.org/kv/goredis"
	"code.byted.org/kv/redis-v6"
)

type Storage struct {
	Client *goredis.Client
}

func New(client *goredis.Client) kv.Storage {
	return &Storage{Client: client}
}

func (s *Storage) NewPipeline(ctx context.Context) kv.Pipeline {
	return &PipelineImpl{s.Client.WithContext(ctx).Pipeline()}
}

func (s *Storage) Incr(ctx context.Context, key string) (int64, error) {
	return s.Client.WithContext(ctx).Incr(key).Result()
}

func (s *Storage) IncrBy(ctx context.Context, key string, value int64) (int64, error) {
	return s.Client.WithContext(ctx).IncrBy(key, value).Result()
}

func (s *Storage) DecrBy(ctx context.Context, key string, value int64) (int64, error) {
	return s.Client.WithContext(ctx).DecrBy(key, value).Result()
}

func (s *Storage) Set(ctx context.Context, key, value string, expiration time.Duration) (string, error) {
	return s.Client.WithContext(ctx).Set(key, value, expiration).Result()
}

func (s *Storage) SetNX(ctx context.Context, key, value string, expiration time.Duration) (ok bool, err error) {
	return s.Client.WithContext(ctx).SetNX(key, value, expiration).Result()
}

func (s *Storage) Get(ctx context.Context, key string) (string, error) {
	return s.Client.WithContext(ctx).Get(key).Result()
}

func (s *Storage) CompareAndSet(ctx context.Context, key, oldValue, newValue string, expiration time.Duration) (kv.RetType, error) {
	ret := s.Client.WithContext(ctx).CasEx(key, oldValue, newValue, expiration)
	return kv.RetType(ret.Val()), ret.Err()
}

func (s *Storage) CompareAndDelete(ctx context.Context, key, value string) (kv.RetType, error) {
	ret := s.Client.WithContext(ctx).Cad(key, value)
	return kv.RetType(ret.Val()), ret.Err()
}

func (s *Storage) Expire(ctx context.Context, key string, expiration time.Duration) (bool, error) {
	return s.Client.WithContext(ctx).Expire(key, expiration).Result()
}

func (s *Storage) Exists(ctx context.Context, keys ...string) (int64, error) {
	return s.Client.WithContext(ctx).Exists(keys...).Result()
}

func (s *Storage) Del(ctx context.Context, key string) error {
	ret := s.Client.WithContext(ctx).Del(key)
	return ret.Err()
}

func (s *Storage) RPush(ctx context.Context, key string, values []string) (int64, error) {
	ifs := make([]interface{}, 0, len(values))
	for index := range values {
		ifs = append(ifs, values[index])
	}
	return s.Client.WithContext(ctx).RPush(key, ifs...).Result()
}

func (s *Storage) LRange(ctx context.Context, key string, start, stop int64) ([]string, error) {
	return s.Client.WithContext(ctx).LRange(key, start, stop).Result()
}

func (s *Storage) LPop(ctx context.Context, key string) (string, error) {
	return s.Client.WithContext(ctx).LPop(key).Result()
}

func (s *Storage) LTrim(ctx context.Context, key string, start, stop int64) error {
	return s.Client.WithContext(ctx).LTrim(key, start, stop).Err()
}
func (s *Storage) LLen(ctx context.Context, key string) (int64, error) {
	return s.Client.WithContext(ctx).LLen(key).Result()
}

func (s *Storage) HSet(ctx context.Context, key, field, value string) (bool, error) {
	return s.Client.WithContext(ctx).HSet(key, field, value).Result()
}

func (s *Storage) HDel(ctx context.Context, key string, field ...string) (int64, error) {
	return s.Client.WithContext(ctx).HDel(key, field...).Result()
}

func (s *Storage) HKeys(ctx context.Context, key string) ([]string, error) {
	return s.Client.WithContext(ctx).HKeys(key).Result()
}

func (s *Storage) HGetSet(ctx context.Context, key, field string, value interface{}) (string, error) {
	return s.Client.WithContext(ctx).HGetSet(key, field, value).Result()
}

func (s *Storage) HGetAll(ctx context.Context, key string) (map[string]string, error) {
	return s.Client.WithContext(ctx).HGetAll(key).Result()
}

func (s *Storage) HMSet(ctx context.Context, key string, fields map[string]interface{}) (string, error) {
	return s.Client.WithContext(ctx).HMSet(key, fields).Result()
}

func (s *Storage) HScan(ctx context.Context, key, match string, cursor uint64, count int64) ([]string, uint64, error) {
	keys, newCursor, err := s.Client.WithContext(ctx).HScan(key, cursor, match, count).Result()
	return keys, newCursor, err
}

func (s *Storage) HGet(ctx context.Context, key, field string) (string, error) {
	value, err := s.Client.WithContext(ctx).HGet(key, field).Result()
	return value, err
}
func (s *Storage) HLen(ctx context.Context, key string) (int64, error) {
	return s.Client.WithContext(ctx).HLen(key).Result()
}

func (s *Storage) ZRem(ctx context.Context, key string, members ...interface{}) error {
	_, err := s.Client.WithContext(ctx).ZRem(key, members...).Result()
	return err
}
func (s *Storage) ZAdd(ctx context.Context, key string, members ...kv.ZMember) error {
	z := make([]redis.Z, len(members))
	for idx, entry := range members {
		z[idx] = redis.Z{
			Score:  entry.Score,
			Member: entry.Member,
		}
	}
	_, err := s.Client.WithContext(ctx).ZAdd(key, z...).Result()
	return err
}
func (s *Storage) ZRangeWithScore(ctx context.Context, key string, start, stop int64) ([]kv.ZMember, error) {
	z, err := s.Client.WithContext(ctx).ZRangeWithScores(key, start, stop).Result()
	if err != nil {
		return nil, err
	}
	members := make([]kv.ZMember, len(z))
	for idx, entry := range z {
		members[idx] = kv.ZMember{
			Member: entry.Member,
			Score:  entry.Score,
		}
	}
	return members, nil
}

func (s *Storage) ZScore(ctx context.Context, key, member string) (float64, error) {
	return s.Client.WithContext(ctx).ZScore(key, member).Result()
}

func (s *Storage) GetShardCount(ctx context.Context) (int64, error) {
	return s.Client.WithContext(ctx).GetShardCount().Result()
}
